using System;
using System.Collections.Generic;
using System.Text;
using System.Xml.Serialization;

namespace OneStop.Calculations
{
    #region Enums

    public enum SegmentMode
    {
        CCR,
        OC
    }

    public enum SegmentType
    {
        DESCENT,
        CONST,
        ASCENT,
        WAYPOINT,
        DECO,
        SURFACE,
        GASSWITCH,
        SETPOINTSWITCH
    }

    #endregion

    /// <summary>
    /// Abstract Segment Class. Defines basic attributes and functions of all dive segments.
	/// </summary>
	public class SegmentAbstract : OneStopBase
    {
        #region Members

        internal double depth = 0; // Depth of segment (typically end)
        internal double setpoint = Prefs.Current.SetpointDefault; // For CCR (=0 for Open Circuit)
        internal Gas gas = new Gas(); // Gas used
        internal double time = 0; // Segment time
        internal double runTime = 0; // Runtime in profile
        internal bool enable = true; // Is this segment enabled (i.e. used)
        internal SegmentType type = SegmentType.CONST; // type of segment, as per below

        #endregion

        #region Properties

        public WarningList Warnings { get; set; }

        public double Gf { get; set; }

        /// <summary> 
        /// Gets/sets segment type (CONST, ASCENT, DESCENT, DECO, WAYPOINT, SURFACE).
        /// </summary>
        virtual public SegmentType Type
        {
            get
            {
                return type;
            }

            set
            {
                type = value;
                NotifyPropertyChanged("Type");
            }
        }

        /// <summary>
        /// Gets Equivalent Narcosis Depth (END) in msw (fsw).
        /// </summary>
        virtual public double END
        {
            get
            {
                double pAbsolute = depth + Prefs.Current.PAmb; // msw (fsw)
                double fN2 = gas.FN2;
                double fHe = gas.FHe;
                double pInert;
                double ppN2Inspired;

                // Set inspired gas fractions.
                if (setpoint > 0)
                {
                    // Rebreather mode
                    // Determine pInert by subtracting absolute oxygen pressure (msw), or force to zero if no inert fraction
                    pInert = ((fHe + fN2) > 0) ? pAbsolute - setpoint * Prefs.Current.PConversion : 0;
                    ppN2Inspired = pInert > 0 ? (pInert * fN2) / (fHe + fN2) : 0;
                }

                // Open circuit mode
                else
                    ppN2Inspired = (pAbsolute) * fN2;
                double end = ppN2Inspired / 0.79 - Prefs.Current.PAmb;
                return end > 0 ? end : 0; // Only return positive numbers.
            }
        }

        /// <summary>
        /// Gets PPO2 of dive segment.
        /// </summary>
        virtual public double PPO2
        {
            get
            {
                double ppO2 = this.Setpoint;

                if (this.Mode == SegmentMode.OC)
                {
                    ppO2 = this.Gas.FO2 * ((this.Depth / 10) + 1);
                }

                return ppO2;
            }
        }

        virtual public double InspiredFHe
        {
            get
            {
                double fHe = 0;

                // Set inspired gas fractions.
                if (setpoint > 0)
                {
                    double pAbsolute = depth + Prefs.Current.PAmb; // msw (fsw)
                    double pInert;
                    pInert = ((gas.FHe + gas.FN2) > 0) ? pAbsolute - setpoint * Prefs.Current.PConversion : 0;
                    fHe = pInert * gas.FHe / pAbsolute;
                }
                else
                {
                    fHe = this.gas.FHe;
                }

                return fHe;
            }
        }

        virtual public double InspiredFN2
        {
            get
            {
                return 1 - this.InspiredFHe - this.InspiredFO2;
            }
        }

        virtual public double InspiredFO2
        {
            get
            {
                double fO2 = 0;

                // Set inspired gas fractions.
                if (setpoint > 0)
                {
                    double pAbsolute = depth + Prefs.Current.PAmb;
                    fO2 = setpoint * Prefs.Current.PConversion / pAbsolute;
                }
                else
                {
                    fO2 = this.Gas.FO2;
                }

                return fO2;
            }
        }

        /// <summary>
        /// Gets PPO2 for OC gas at depth, or else inspired PPO2 on CCR.
        /// </summary>
        virtual public double PPN2
        {
            get
            {
                double pAbsolute = depth + Prefs.Current.PAmb; // msw (fsw)
                double fN2 = gas.FN2;
                double fHe = gas.FHe;
                double pInert;
                double ppN2Inspired;

                // Set inspired gas fractions.
                if (setpoint > 0)
                {
                    // Rebreather mode
                    // Determine pInert by subtracting absolute oxygen pressure (msw), or force to zero if no inert fraction
                    pInert = ((fHe + fN2) > 0) ? pAbsolute - setpoint * Prefs.Current.PConversion : 0;
                    ppN2Inspired = pInert > 0 ? (pInert * fN2) / (fHe + fN2) : 0;
                }
                else
                {
                    ppN2Inspired = pAbsolute * fN2;
                }

                return ppN2Inspired / 10;
            }
        }

        virtual public double Flush
        {
            get
            {
                double pAbsolute = depth + Prefs.Current.PAmb;

                return (pAbsolute * gas.FO2) / 10;
            }
        }

        /// <summary>
        /// Whether in CCR or OC mode for the given segment.
        /// </summary>
        virtual public SegmentMode Mode
        {
            get
            {
                return (this.Setpoint > 0) ? SegmentMode.CCR : SegmentMode.OC;
            }
        }
        
        /// <summary> 
        /// Gets/sets depth of segment.
        /// </summary>
		virtual public double Depth
		{
			get
			{
				return depth;
			}
			
			set
			{
				depth = value;
                NotifyPropertyChanged("Depth");
			}			
		}

        /// <summary> 
        /// Gets/sets time of segment.
        /// </summary>
		virtual public double Time
		{
			get
			{
				return time;
			}
			
			set
			{
				time = value;
                NotifyPropertyChanged("Time");
			}			
		}

        /// <summary>
        /// Gets the minute part of the segment time.
        /// </summary>
        virtual public int TimeMin
        {
            get
            {
                return (int)time;
            }
        }

        /// <summary>
        /// Gets the seconds part of the segment time.
        /// </summary>
        virtual public int TimeSec
        {
            get
            {
                return  (int)((this.Time - (double)this.TimeMin) * 60);
            }
        }

        /// <summary>
        /// Gets the time in Min:Sec format.
        /// </summary>
        virtual public string TimeString
        {
            get
            {
                string sec = this.TimeSec.ToString();

                if (sec.Length == 1)
                {
                    sec = "0" + sec;
                }

                return string.Format("{0}:{1}", this.TimeMin, sec);
            }
        }

        /// <summary> 
        /// Gets/sets Gas object used on this segment.
        /// </summary>
		virtual public Gas Gas
		{
			get
			{
				return gas;
			}
			
			set
			{
                gas = value;
                NotifyPropertyChanged("Gas");
			}			
		}

        /// <summary> 
        /// Gets/sets setpoint used on this segment (or 0 for open circuit).
        /// </summary>
		virtual public double Setpoint
		{
			get
			{
				return setpoint;
			}
			
			set
			{
                setpoint = value;
                NotifyPropertyChanged("Setpoint");
			}			
		}

        /// <summary> 
        /// Gets/sets runTime of this segment.
        /// </summary>
		virtual public double RunTime
		{
			get
			{
				return runTime;
			}
			
			set
			{
                runTime = value;
                NotifyPropertyChanged("RunTime");
			}			
		}

        /// <summary> 
        /// Gets/sets whether this segment is enabled. 
        /// Typically used for input segments to dive profile.
        /// </summary>
		virtual public bool Enable
		{
			get
			{
				return enable;
			}
			
			set
			{
                enable = value;
                NotifyPropertyChanged("Enable");
			}			
		}

		/// <summary> 
        /// Gets segment type as short symbolic string
        /// </summary>
        virtual public string TypeString
        {
            get
            {
                switch (type)
                {
                    case SegmentType.CONST:
                        return "-";

                    case SegmentType.ASCENT:
                        return "^";

                    case SegmentType.DESCENT:
                        return "v";

                    case SegmentType.DECO:
                        return "~";

                    case SegmentType.WAYPOINT:
                        return ".";
                    case SegmentType.GASSWITCH:
                        return "#";
                    case SegmentType.SETPOINTSWITCH:
                        return "*";

                    default:
                        return " ";
                }
            }
        }

        /// <summary>
        /// 
        /// </summary>
        virtual public string TypeName
        {
            get
            {
                switch (type)
                {
                    case SegmentType.CONST:
                        return "Constant";
                    case SegmentType.ASCENT:
                        return "Ascent";
                    case SegmentType.DESCENT:
                        return "Descent";
                    case SegmentType.DECO:
                        return "Deco";
                    case SegmentType.WAYPOINT:
                        return "Waypoint";
                    case SegmentType.GASSWITCH:
                        return "Gas Switch";
                    case SegmentType.SETPOINTSWITCH:
                        return "SP Switch";
                    default:
                        return " ";
                }
            }
        }

        /// <summary> 
        /// Override the clone method due to the need for deep cloning.
        /// </summary>
        public virtual object Clone()
        {
            try
            {
                SegmentAbstract copy = (SegmentAbstract)base.MemberwiseClone(); // Shallow clone
                copy.gas = (Gas)gas.Clone();
                return copy;
            }
            catch (Exception)
            {
                throw new Exception("Never happens");
            }
        }

        /// <summary>
        /// Returns a string representation of segment.
        /// </summary> 
        public override string ToString()
        {
            int timeMins, timeSeconds;

            timeMins = (int)time;
            timeSeconds = (int)((time - (double)timeMins) * 60);

            string gasString = null;

            if (gas != null)
            {
                gasString = gas.ToString();
            }

            return string.Format("{7} {0}{1} for {2}:{3}min ({4}) using {9} {5} @ {6}; PPN2: {8}; Gf: {10}", depth, Prefs.Current.DepthShortString, timeMins, timeSeconds, (int)Math.Round(runTime), gasString, this.PPO2.ToString("F2"), this.TypeString, this.PPN2.ToString("F2"), this.Mode, this.Gf.ToString("F2"));
        }

        /// <summary> 
        /// Gets volume of gas used in litres (cuft).
        /// </summary
        public virtual double GasUsed()
        {
            return -1;
        }

        #endregion

        #region Constructors

        public SegmentAbstract()
        {
            this.Warnings = new WarningList();
        }

        #endregion
	}
}
